/*
 * Copyright (C) 2012 The MjSip Open Source Project
 * Copyright (C) 2005 Luca Veltri - University of Parma - Italy
 * 
 * This file is part of MjSip (http://www.mjsip.org)
 * 
 * Sipdroid is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This source code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package com.cvtt.voice.simple;

import java.io.IOException;
import java.net.SocketException;

import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.ToneGenerator;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;

import com.cvtt.sip.SIPEngine;
import com.cvtt.voice.VoiceConfig;
import com.cvtt.voice.codec.Codecs;
import com.cvtt.voice.simple.net.CNativeSocket;
import com.cvtt.voice.simple.net.RtpPacket;
import com.cvtt.voice.simple.net.RtpSocket;

/**
 * RTPStreamReceiver is a generic stream receiver. It receives packets from RTP
 * and writes them into an OutputStream.
 */
public class RTPStreamReceiver extends Thread {

	/** Whether working in debug mode. */
	public static boolean DEBUG = true;

	/** Payload type */
	Codecs.Map p_type;

	static String codec = "";

	/** Size of the read buffer */
	public static final int BUFFER_SIZE = 1024;

	/**
	 * Maximum blocking time, spent waiting for reading new bytes [milliseconds]
	 */
	public static final int SO_TIMEOUT = 1000;

	/** The RtpSocket */
	RtpSocket rtp_socket = null;

	/** Whether it is running */
	boolean running;

	ContentResolver cr;

	public static int speakermode = -1;
	public static boolean bluetoothmode;

	public static int audioMode = -1;
	public static boolean speakerOn = false;

	static boolean samsung;

	private AudioManager am;
	private int streamType;
	private int oldVolume;

	private Speex spx;

	/**
	 * Constructs a RTPStreamReceiver.
	 * 
	 * @param output_stream
	 *            the stream sink
	 * @param socket
	 *            the local receiver SipdroidSocket
	 */
	public RTPStreamReceiver(CNativeSocket socket, Codecs.Map payload_type) {
		init(socket);
		p_type = payload_type;
		am = (AudioManager) VoiceConfig.getVoiceConfig().getContext()
				.getSystemService(Context.AUDIO_SERVICE);
		oldVolume = -1;
		// streamType = AudioManager.STREAM_VOICE_CALL;
		// Modified by huah in 2012-08-03
		//spx = Speex.getInstance();
	}

	/** Inits the RTPStreamReceiver */
	private void init(CNativeSocket socket) {
		if (socket != null)
			rtp_socket = new RtpSocket(socket);
	}

	/** Whether is running */
	public boolean isRunning() {
		return running;
	}

	/** Stops running */
	public void halt() {
		running = false;
	}

	void bluetooth() {
		speaker(AudioManager.MODE_IN_CALL);
		enableBluetooth(!bluetoothmode);
	}

	static boolean was_enabled;

	public static void enableBluetooth(boolean mode) {
		if (bluetoothmode != mode && (!mode || isBluetoothAvailable())) {
			if (mode)
				was_enabled = true;
			Bluetooth.enable(bluetoothmode = mode);
		}
	}

	void cleanupBluetooth() {
		if (was_enabled) {
			enableBluetooth(true);
			try {
				sleep(3000);
			} catch (InterruptedException e) {
			}
			android.os.Process.killProcess(android.os.Process.myPid());
		}
	}

	public static boolean isBluetoothAvailable() {
		if (VoiceConfig.headset > 0 || VoiceConfig.docked > 0)
			return false;
		if (!isBluetoothSupported())
			return false;
		return Bluetooth.isAvailable();
	}

	public static boolean isBluetoothSupported() {
		if (Integer.parseInt(Build.VERSION.SDK) < 8)
			return false;
		return Bluetooth.isSupported();
	}

	public int speaker(int mode) {
		int old = speakermode;

		if (mode == old)
			return old;
		enableBluetooth(false);
		// Modified by huah in 2012-06-28
		// setMode(speakermode = mode);
		reset();

		return old;
	}

	static ToneGenerator ringbackPlayer;
	static int oldvol = -1;

	// static int stream() {
	// // Modified by huah in 2012-06-28
	// return (audioMode == AudioManager.MODE_IN_CALL) ?
	// AudioManager.STREAM_VOICE_CALL
	// : AudioManager.STREAM_MUSIC;
	// // return AudioManager.STREAM_VOICE_CALL;
	// }

	public void setStreamType(int streamType) {
		this.streamType = streamType;
	}

	public int getStreamType() {
		// Modified by huah in 2012-08-16
		// int audioMode = am.getMode();
		streamType = (audioMode == AudioManager.MODE_IN_CALL) ? AudioManager.STREAM_VOICE_CALL
				: AudioManager.STREAM_MUSIC;// AudioManager.STREAM_RING;
		return streamType;
	}

	// Modified by huah in 2012-06-28
	// public static synchronized void ringback(boolean ringback) {
	// if (ringback && ringbackPlayer == null) {
	//
	// // TODO:
	// enableBluetooth(PreferencesConfig.getBoolean(VoiceConfig.CONFIG_BLUETOOTH,
	// VoiceConfig.DEFAULT_BLUETOOTH));
	//
	// ringbackPlayer = new ToneGenerator(stream(),
	// (int) (ToneGenerator.MAX_VOLUME * 2 * VoiceConfig
	// .getEarGain()));
	// ringbackPlayer.startTone(ToneGenerator.TONE_SUP_RINGTONE);
	// } else if (!ringback && ringbackPlayer != null) {
	// ringbackPlayer.stopTone();
	// ringbackPlayer.release();
	// ringbackPlayer = null;
	// if (QQVoiceCore.callState == QQVoiceCore.CALLSTATE_IDLE) {
	//
	// enableBluetooth(false);
	//
	// }
	// }
	// }

	double smin = 200, s;
	public static int nearend;

	private void adjustData(short[] lin, int off, int len, int gain) {
		int i, j;
		for (i = 0; i < len; i++) {
			j = lin[i + off] * gain;

			if (j > 32767)
				j = 32767;
			else if (j < -32767)
				j = -32767;
			lin[i + off] = (short) j;
		}
	}

	void calc(short[] lin, int off, int len) {
		int i, j;
		double sm = 30000, r;

		for (i = 0; i < len; i += 5) {
			j = lin[i + off];
			s = 0.03 * Math.abs(j) + 0.97 * s;
			if (s < sm)
				sm = s;
			if (s > smin)
				nearend = 6000 * mu / 5;
			else if (nearend > 0)
				nearend--;
		}
		for (i = 0; i < len; i++) {
			j = lin[i + off];
			if (j > 6550)
				lin[i + off] = 6550 * 5;
			else if (j < -6550)
				lin[i + off] = -6550 * 5;
			else
				lin[i + off] = (short) (j * 5);
		}
		r = (double) len / (100000 * mu);
		if (sm > 2 * smin || sm < smin / 2)
			smin = sm * r + smin * (1 - r);
	}

	// Added by huah in 2012-06-27

	void calc1(short[] lin, int off, int len) {
		int i, j;

		for (i = 0; i < len; i++) {
			j = lin[i + off];
			lin[i + off] = (short) (j >> 1);
		}
	}

	void calc5(short[] lin, int off, int len) {
		int i, j;

		for (i = 0; i < len; i++) {
			j = lin[i + off];
			if (j > 16350)
				lin[i + off] = 16350 << 1;
			else if (j < -16350)
				lin[i + off] = -16350 << 1;
			else
				lin[i + off] = (short) (j << 1);
		}
	}

	void calc10(short[] lin, int off, int len) {
		int i, j;

		// for (i = 0; i < len; i++) {
		// j = lin[i + off];
		// if (j > 8150)
		// lin[i + off] = 8150 << 2;
		// else if (j < -8150)
		// lin[i + off] = -8150 << 2;
		// else
		// lin[i + off] = (short) (j << 2);
		// }

		for (i = 0; i < len; i++) {
			j = lin[i + off];
			if (j > 6550)
				lin[i + off] = 6550 * 5;
			else if (j < -6550)
				lin[i + off] = -6550 * 5;
			else
				lin[i + off] = (short) (j * 5);
		}
	}

	// end

	static long down_time;

	static boolean restored;

	void initMode() {
		// TODO:
		// if (QQVoiceCore.callState == CallAgent.UA_STATE_INCOMING_CALL &&
		// (Receiver.pstn_state == null || Receiver.pstn_state.equals("IDLE")))
		String model = Build.MODEL;
		samsung = Build.MODEL.contains("SAMSUNG")
				|| Build.MODEL.contains("SPH-") || Build.MODEL.contains("SGH-")
				|| Build.MODEL.contains("GT-");

		// Modified by huah in 2012-06-28
	}

	public static float good, late, lost, loss;
	double avgheadroom;
	public static int timeout;
	int seq;

	void empty() {
		try {
			rtp_socket.getDatagramSocket().setSoTimeout(1);
			for (;;)
				rtp_socket.receive(rtp_packet);
		} catch (SocketException e2) {
			e2.printStackTrace();
		} catch (IOException e) {
		}
		try {
			rtp_socket.getDatagramSocket().setSoTimeout(SO_TIMEOUT);
		} catch (SocketException e2) {
			e2.printStackTrace();
		}
		seq = 0;
	}

	RtpPacket rtp_packet;
	AudioTrack track;
	int maxjitter, minjitter, minjitteradjust, minheadroom;
	int cnt, cnt2, user, luser, luser2, lserver;
	public static int jitter, mu;

	public void reset() {
		synchronized (this) {
			AudioTrack oldtrack;

			p_type.codec.init();
			codec = p_type.codec.getTitle();
			mu = p_type.codec.samp_rate() / 8000;
			maxjitter = AudioTrack.getMinBufferSize(p_type.codec.samp_rate(),
					AudioFormat.CHANNEL_CONFIGURATION_MONO,
					AudioFormat.ENCODING_PCM_16BIT);
			if (maxjitter < 2 * 2 * BUFFER_SIZE * 3 * mu)
				maxjitter = 2 * 2 * BUFFER_SIZE * 3 * mu;
			oldtrack = track;

			track = new AudioTrack(getStreamType(), p_type.codec.samp_rate(),
					AudioFormat.CHANNEL_CONFIGURATION_MONO,
					AudioFormat.ENCODING_PCM_16BIT, maxjitter,
					AudioTrack.MODE_STREAM);
			maxjitter /= 2 * 2;
			minjitter = minjitteradjust = 500 * mu;
			jitter = 875 * mu;
			minheadroom = maxjitter * 2;
			timeout = 1;
			luser = luser2 = -8000 * mu;
			cnt = cnt2 = user = lserver = 0;
			if (oldtrack != null) {
				oldtrack.stop();
				oldtrack.release();
			}

			if (speakerOn && (streamType == AudioManager.STREAM_MUSIC))
				maximizeVolume();
			else
				restoreVolume();

			if (samsung)
				RTPStreamSender.changed = true;
		}
	}

	void write(short a[], int b, int c) {
		synchronized (this) {
			//TODO:Modified by huah in 2012-08-11
			int writed_num = track.write(a, b, c);
			user += writed_num;

			// TODO:Modified by huah in 2012-08-03 for debug Speex
//			if(writed_num > 0)
//				spx.doPlay(a, b, writed_num);
		}
	}

	PowerManager.WakeLock pwl, pwl2;

	void lock(boolean lock) {
		if (lock) {
			if (pwl2 == null) {
				PowerManager pm = (PowerManager) VoiceConfig.getVoiceConfig().getContext()
						.getSystemService(Context.POWER_SERVICE);
				pwl2 = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
						"uCalling.Receiver");
				pwl2.acquire();
			}
		} else if (pwl2 != null) {
			pwl2.release();
			pwl2 = null;
		}
		if (keepon) {
			if (lock) {
				if (pwl == null) {
					PowerManager pm = (PowerManager) VoiceConfig.getVoiceConfig().getContext()
							.getSystemService(Context.POWER_SERVICE);
					pwl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
							| PowerManager.ACQUIRE_CAUSES_WAKEUP,
							"uCalling.Receiver");
					pwl.acquire();
				}
			} else if (pwl != null) {
				pwl.release();
				pwl = null;
			}
		}
	}

	boolean keepon;

	/** Runs it in a new Thread. */
	public void run() {
		boolean nodata = VoiceConfig.DEFAULT_NODATA;
		keepon = false;

		int eargain = (int) (VoiceConfig.getEarGain() * 4);

		if (rtp_socket == null) {
			if (DEBUG)
				println("ERROR: RTP socket is null");
			return;
		}

		byte[] buffer = new byte[BUFFER_SIZE + 12];
		rtp_packet = new RtpPacket(buffer, 0);

		if (DEBUG)
			println("Reading blocks of max " + buffer.length + " bytes");

		running = true;
		enableBluetooth(VoiceConfig.DEFAULT_BLUETOOTH);
		restored = false;

		android.os.Process
				.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
		// am = (AudioManager) VoiceConfig.mContext
		// .getSystemService(Context.AUDIO_SERVICE);
		cr = VoiceConfig.getVoiceConfig().getContext().getContentResolver();
		// saveSettings();
		Settings.System.putInt(cr, Settings.System.WIFI_SLEEP_POLICY,
				Settings.System.WIFI_SLEEP_POLICY_NEVER);
		am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER,
				AudioManager.VIBRATE_SETTING_OFF);
		am.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION,
				AudioManager.VIBRATE_SETTING_OFF);
		// if (oldvol == -1)
		// oldvol = am.getStreamVolume(AudioManager.STREAM_MUSIC);
		initMode();
		reset();
		short lin[] = new short[BUFFER_SIZE];
		short lin2[] = new short[BUFFER_SIZE];
		int server, headroom, todo, len = 0, m = 1, expseq, getseq, vm = 1, gap, gseq;
		ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
				(int) (ToneGenerator.MAX_VOLUME * 2 * VoiceConfig.getEarGain()));
		track.play();
		System.gc();
		while (running) {
			// lock(true);
			if (SIPEngine.getSIPEngine().getCallState() == SIPEngine.CALLSTATE_INCOMING) {
				try {
					sleep(100);
				} catch (InterruptedException e) {
				}
				continue;
			}

			try {
				rtp_socket.receive(rtp_packet);
				if (timeout != 0) {
					//TODO:Modified by huah in 2012-08-11
//					tg.stopTone();
					track.pause();
					//TODO:Modified by huah in 2012-08-11
					for (int i = maxjitter * 2; i > 0; i -= BUFFER_SIZE)
						write(lin2, 0, i > BUFFER_SIZE ? BUFFER_SIZE : i);
					cnt += maxjitter * 2;
					track.play();
					empty();
				}
				timeout = 0;
			} catch (IOException e) {
				if (timeout == 0 && nodata) {
					//TODO:Modified by huah in 2012-08-11
					//tg.startTone(ToneGenerator.TONE_SUP_RINGTONE);
				}
				rtp_socket.getDatagramSocket().disconnect();
				if (++timeout > 22) {
					// TODO:
					// Receiver.engine(VoiceConfig.mContext).rejectcall();
					SIPEngine.getSIPEngine().endCall(true);
					break;
				}
			}
			if (running && timeout == 0) {
				gseq = rtp_packet.getSequenceNumber();
				if (seq == gseq) {
					m++;
					continue;
				}
				
				//TODO:Modified by huah in 2012-08-11
//				len = p_type.codec.decode(buffer, lin,
//						rtp_packet.getPayloadLength());
//				
//				if(len > 0)
//				{
//					// Added by huah in 2012-06-28
//					adjustData(lin, 0, len, eargain);
//					
//					write(lin, 0, len);
//				}

				server = track.getPlaybackHeadPosition();
				headroom = user - server;

				if (headroom > 2 * jitter)
					cnt += len;
				else
					cnt = 0;

				if (lserver == server)
					cnt2++;
				else
					cnt2 = 0;

				if (cnt <= 500 * mu || cnt2 >= 2 || headroom - jitter < len
						|| p_type.codec.number() != 8
						|| p_type.codec.number() != 0) {
					if (rtp_packet.getPayloadType() != p_type.number
							&& p_type.change(rtp_packet.getPayloadType())) {
						reset();
					}
					len = p_type.codec.decode(buffer, lin,
							rtp_packet.getPayloadLength());

					// Added by huah in 2012-06-28
					adjustData(lin, 0, len, eargain);

				}

				avgheadroom = avgheadroom * 0.99 + (double) headroom * 0.01;
				if (headroom < minheadroom)
					minheadroom = headroom;
				if (headroom < 250 * mu) {
					late++;
					if (good != 0 && lost / good < 0.01)
						if (late / good > 0.01
								&& jitter + minjitteradjust < maxjitter) {
							jitter += minjitteradjust;
							late = 0;
							luser2 = user;
							minheadroom = maxjitter * 2;
						}
					todo = jitter - headroom;
					//TODO:Modified by huah in 2012-08-11
					//write(lin2, 0, todo > BUFFER_SIZE ? BUFFER_SIZE : todo);
				}

				if (cnt > 500 * mu && cnt2 < 2) {
					todo = headroom - jitter;
					if (todo < len)
						write(lin, todo, len - todo);
				} else
					write(lin, 0, len);

				if (seq != 0) {
					getseq = gseq & 0xff;
					expseq = ++seq & 0xff;
					if (m == RTPStreamSender.m)
						vm = m;
					gap = (getseq - expseq) & 0xff;
					if (gap > 0) {
						if (gap > 100)
							gap = 1;
						loss += gap;
						lost += gap;
						good += gap - 1;
					} else {
						if (m < vm)
							loss++;
					}
					good++;
					if (good > 100) {
						good *= 0.99;
						lost *= 0.99;
						loss *= 0.99;
						late *= 0.99;
					}
				}
				m = 1;
				seq = gseq;

				if (user >= luser + 8000 * mu
						&& (SIPEngine.getSIPEngine().getCallState() == SIPEngine.CALLSTATE_ON || SIPEngine.getSIPEngine().getCallState() == SIPEngine.CALLSTATE_OUTGOING)) {

					luser = user;
					if (user >= luser2 + 160000 * mu && good != 0
							&& lost / good < 0.01 && avgheadroom > minheadroom) {
						int newjitter = (int) avgheadroom - minheadroom
								+ minjitter;
						if (jitter - newjitter > minjitteradjust)
							jitter = newjitter;
						minheadroom = maxjitter * 2;
						luser2 = user;
					}
				}
				lserver = server;
			}
		}
		// lock(false);
		track.stop();
		track.release();
		// Modified by huah in 2012-08-05
		track = null;

		tg.stopTone();
		tg.release();

		// restoreSettings();
		enableBluetooth(false);

		restoreVolume();
		// oldvol = -1;
		p_type.codec.close();
		rtp_socket.close();
		rtp_socket = null;
		codec = "";

		// TODO:Modified by huah in 2012-08-03 for debug Speex
//		spx.destroy();

		if (DEBUG)
			println("rtp receiver terminated");

		cleanupBluetooth();
	}

	private void setStreamVolume(final int stream, final int vol,
			final int flags) {
		(new Thread() {
			public void run() {
				// AudioManager am = (AudioManager)
				// VoiceConfig.mContext.getSystemService(Context.AUDIO_SERVICE);
				am.setStreamVolume(stream, vol, flags);
				// if (stream == getStreamType())
				// restored = true;
			}
		}).start();
	}

	static float gain, ogain;

	private void maximizeVolume() {

		oldVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
		if (track != null)
			track.setStereoVolume(AudioTrack.getMaxVolume(),
					AudioTrack.getMaxVolume());
		am.setStreamVolume(AudioManager.STREAM_MUSIC,
				am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
		// setStreamVolume(AudioManager.STREAM_MUSIC,
		// am.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * 3 / 4, 0);
	}

	private void restoreVolume() {
		if (oldVolume != -1)
			am.setStreamVolume(AudioManager.STREAM_MUSIC, oldVolume, 0);
		oldVolume = -1;
	}

	/** Debug output */
	private static void println(String str) {
		if (VoiceConfig.LOG_ENABLE == false)
			return;
		System.out.println("RTPStreamReceiver: " + str);
	}

	public static int byte2int(byte b) { // return (b>=0)? b : -((b^0xFF)+1);
		// return (b>=0)? b : b+0x100;
		return (b + 0x100) % 0x100;
	}

	public static int byte2int(byte b1, byte b2) {
		return (((b1 + 0x100) % 0x100) << 8) + (b2 + 0x100) % 0x100;
	}

	public static String getCodec() {
		return codec;
	}
}
